package com.zhyl.detectibrary.util;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.RectF;
import android.os.SystemClock;

import org.tensorflow.lite.gpu.CompatibilityList;
import org.tensorflow.lite.support.image.ImageProcessor;
import org.tensorflow.lite.support.image.TensorImage;
import org.tensorflow.lite.support.image.ops.Rot90Op;
import org.tensorflow.lite.support.label.Category;
import org.tensorflow.lite.task.core.BaseOptions;
import org.tensorflow.lite.task.vision.detector.Detection;
import org.tensorflow.lite.task.vision.detector.ObjectDetector;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.stream.Collectors;

/**
 * 图像稳定检测，用于检测识别物品是否稳定
 */
public class ObjectDetectorHelper {
    /**
     * 相似度
     */
    private static final Float threshold = 0.5f;
    /**
     * 线程数
     */
    private static final Integer numThreads = 2;
    /**
     * 检测最大返回对象结果数量
     */
    private static final Integer maxResults = 8;

    /**
     * 当前的处理器代理
     */
    Integer currentDelegate = DELEGATE_CPU;
    final static int DELEGATE_CPU = 0;
    final static int DELEGATE_GPU = 1;
    final static int DELEGATE_NNAPI = 2;

    Context context;
    DetectorListener objectDetectorListener;

    ObjectDetector objectDetector;


    /**
     * 检测结果对列
     */
    private Queue<List<Detection>> detectResultQueue = new LinkedList<>();
    /**
     * 检测结果对列数量，几次检测结果相同，则认为检测稳定
     */
    private static int DETECT_RESULT_QUEUE_CAPACITY = 2;

    private static int DETECT_TIME_INTERVAL = 200;

    private long detectInferenceTime = 0;

    private static final int maxImageWidth = 320, maxImageHeight = 240;

    private boolean inited = false;

    public ObjectDetectorHelper(Context context) {
        this(context, null);
    }

    public ObjectDetectorHelper(Context context, DetectorListener objectDetectorListener) {
        this.objectDetectorListener = objectDetectorListener;
        this.context = context;
        init();
    }


    /**
     * 初始化
     */
    public void init() {
        try {
            AppLisenseHelper.verifyLicence(this.context);
        }catch (Exception e){
            throw new RuntimeException("非法的授权:" + e.getMessage());
        }

        // Create the base options for the detector using specifies max results and score threshold
        ObjectDetector.ObjectDetectorOptions.Builder optionsBuilder = ObjectDetector.ObjectDetectorOptions.builder()
                .setScoreThreshold(threshold)
                .setMaxResults(maxResults);

        // Set general detection options, including number of used threads
        BaseOptions.Builder baseOptionsBuilder = BaseOptions.builder().setNumThreads(numThreads);

        // Use the specified hardware for running the model. Default to CPU
        switch (currentDelegate) {
            case DELEGATE_CPU:
                break;
            case DELEGATE_GPU:
                if (new CompatibilityList().isDelegateSupportedOnThisDevice()) {
                    baseOptionsBuilder.useGpu();
                } else {
                    if (objectDetectorListener != null) {
                        objectDetectorListener.onDetectorError("GPU is not supported on this device");
                    }
                }
                break;
            case DELEGATE_NNAPI:
                baseOptionsBuilder.useNnapi();
                break;

        }

        optionsBuilder.setBaseOptions(baseOptionsBuilder.build());

        String modelName = "detect_model.tflite";

        try {
            objectDetector = ObjectDetector.createFromFileAndOptions(context, modelName, optionsBuilder.build());
            this.inited = true;
        } catch (FileNotFoundException e) {
            if (objectDetectorListener != null) {
                objectDetectorListener.onDetectorError("始化检测失败,没有找到文件:" + e.getMessage());
            }
        }catch (Exception e) {
            if (objectDetectorListener != null) {
                objectDetectorListener.onDetectorError("初始化检测失败:" + e.getMessage());
            }
        }
    }

    /**
     * 目标检测
     *
     * @param image
     * @param imageRotation
     */
    public void detect(Bitmap image, Integer imageRotation) {
        if (objectDetector == null) {
            init();
        }
        if(!this.inited){
            return;
        }

        // 记录每次检测的时间
        long startTime = SystemClock.uptimeMillis();


        ImageProcessor imageProcessor = new
                ImageProcessor.Builder()
                .add(new Rot90Op(-imageRotation / 90))
                .build();

        image = scaleBitmap(image, maxImageWidth, maxImageHeight);

        // 对图像进行预处理，并将其转换为用于检测的张量图像
        TensorImage tensorImage = imageProcessor.process(TensorImage.fromBitmap(image));

        List<Detection> results = objectDetector.detect(tensorImage);
        detectInferenceTime = SystemClock.uptimeMillis() - startTime;

        // 移除背景
        removeBackgroud(results);

        //增加到识别队列，用于判断是否稳定
        addToDectectResultQueue(results);

        if (DETECT_TIME_INTERVAL > 0) {
            try {
                long sleepTime = DETECT_TIME_INTERVAL - detectInferenceTime;
                if (sleepTime > 0) {
                    Thread.sleep(sleepTime);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        detectInferenceTime = SystemClock.uptimeMillis() - startTime;
        List<DetectItem> itemResult = results.stream().map(item -> {
            DetectItem detectItem = new DetectItem();
            detectItem.setBoundingBox(item.getBoundingBox());
            detectItem.setLabel(item.getCategories().get(0).getLabel());
            detectItem.setScore(item.getCategories().get(0).getScore());
            return detectItem;
        }).collect(Collectors.toList());

        if (objectDetectorListener != null) {
            objectDetectorListener.onDetectorResults(
                    itemResult,
                    detectInferenceTime,
                    tensorImage.getHeight(),
                    tensorImage.getWidth());
        }


    }

    /**
     * 移除背景
     *
     * @param results
     */
    private void removeBackgroud(List<Detection> results) {
        for (int i = results.size(); i > 0; i--) {
            Detection detection = results.get(i - 1);
            List<Category> categories = detection.getCategories();
            for (Category c : categories) {
                if ("background".equals(c.getLabel())) {
                    results.remove(detection);
                    //break;
                }
                // 如果检测到多个物体，且包启托盘
                if (results.size() > 1 && "plate".equals(c.getLabel())) {
                    results.remove(detection);
                } else {
                    // 如果只有一个物体，且是托盘，那么相识度必须大于0.7
                    if ("plate".equals(c.getLabel()) && c.getScore() < 0.7) {
                        results.remove(detection);
                    }
                }
            }
        }
    }

    /**
     * 添加到识别队列
     *
     * @param detectResponse
     */
    private void addToDectectResultQueue(List<Detection> detectResponse) {
        if (detectResultQueue.size() >= DETECT_RESULT_QUEUE_CAPACITY) {
            detectResultQueue.poll();
        }
        detectResultQueue.offer(detectResponse);
    }


    /**
     * 获取识别对像坐标位置转换
     * int left = rect[0];
     * int top = rect[1];
     * int width = rect[2]
     * int height = rect[3]
     */
    public static int[] getDrawRect(RectF boundingBox, Integer imageHeight, Integer imageWidth, int previewWidth, int previewHeight) {
        int objLeft = (int) (boundingBox.left / imageWidth * previewWidth);
        int objTop = (int) (boundingBox.top / imageHeight * previewHeight);
        int objWidth = (int) (boundingBox.width() / imageWidth * previewWidth);
        int objHeight = (int) (boundingBox.height() / imageHeight * previewHeight);
        int[] drawRect = {objLeft, objTop, objWidth, objHeight};
        return drawRect;
    }

    public static RectF getDrawRectF(RectF boundingBox, Integer imageHeight, Integer imageWidth, int previewWidth, int previewHeight) {
        int[] drawRect = getDrawRect(boundingBox,  imageHeight,  imageWidth,  previewWidth,  previewHeight);
        RectF rect = new RectF(drawRect[0], drawRect[1], drawRect[0]+drawRect[2], drawRect[1]+drawRect[3]);
        return rect;
    }

    /**
     * 是否检测到手
     *
     * @return
     */
    public boolean isDetectHand() {
        List<Detection> detectList = detectResultQueue.element();
        for (int i = 0; i < detectList.size(); i++) {
            Detection detectObject = detectList.get(i);
            String label = detectObject.getCategories().get(0).getLabel();
            if ("hand".equals(label)) {
                return true;
            }
        }
        return false;
    }

    /*
     * 物品检测结果是否识别稳定
     */
    public boolean isImageDetectStable() {
        // 允许偏移的倍数
        int offsetScale = 6;//10;
        ArrayList<List<Detection>> detectResponseList = new ArrayList<>();
        detectInferenceTime = 0;

        for (List<Detection> q : detectResultQueue) {
            detectResponseList.add(q);
        }

        //如果队列里的元素没有达到队列长度，则返回不稳定
        if (detectResponseList.size() < DETECT_RESULT_QUEUE_CAPACITY) {
            return false;
        }


        for (int i = 0; i < detectResponseList.size() - 1; i++) {
            List<Detection> firstDetectResult = detectResponseList.get(i);
            List<Detection> nextDetectResult = detectResponseList.get(i + 1);

            //如果检测结果的物品数量不一样多，则不稳定，继续检测
            if (firstDetectResult.size() != nextDetectResult.size()) {
                return false;
            }

            firstDetectResult.sort(detectResultComparator);
            nextDetectResult.sort(detectResultComparator);


            for (int j = 0; j < firstDetectResult.size(); j++) {
                Detection firstDetectObject = firstDetectResult.get(j);
                Detection nextDetectObject = nextDetectResult.get(j);
               /* if (!firstDetectObject.getCode().equals(nextDetectObject.getCode())) {
                    return false;
                }*/
                float firstDetectObjectX = firstDetectObject.getBoundingBox().left;
                float nextDetectObjectX = nextDetectObject.getBoundingBox().left;
                float maxWidth = Math.max(firstDetectObject.getBoundingBox().width(), nextDetectObject.getBoundingBox().width());
                float enableOffsetX = maxWidth / offsetScale;
                if (Math.abs(firstDetectObjectX - nextDetectObjectX) > enableOffsetX) {
                    return false;
                }

                float firstDetectObjectY = firstDetectObject.getBoundingBox().top;
                float nextDetectObjectY = nextDetectObject.getBoundingBox().top;
                float maxHeigth = Math.max(firstDetectObject.getBoundingBox().height(), nextDetectObject.getBoundingBox().height());
                float enableOffsetY = maxHeigth / offsetScale;
                if (Math.abs(firstDetectObjectY - nextDetectObjectY) > enableOffsetY) {
                    return false;
                }
            }
        }

        return true;
    }

    static Comparator detectResultComparator = new Comparator<Detection>() {
        @Override
        public int compare(Detection o1, Detection o2) {

            Float x1 = o1.getBoundingBox().left;
            Float y1 = o1.getBoundingBox().top;
            Float x2 = o2.getBoundingBox().left;
            Float y2 = o2.getBoundingBox().top;

            Float z1 = x1 + y1;
            Float z2 = x2 + y2;

            Integer zCompareResult = z1.compareTo(z2);
            if (zCompareResult != 0) {
                return zCompareResult;
            }

            Integer xCompareResult = x1.compareTo(x2);
            if (xCompareResult != 0) {
                return xCompareResult;
            }


            Integer yCompareResult = y1.compareTo(y2);
            if (yCompareResult != 0) {
                return yCompareResult;
            }
            return 0;
        }
    };

    /**
     * 是否全都在边上
     *
     * @param list
     * @return
     */
    public boolean isAllObjectAtEdges(List<Detection> list) {
        int paddingWith = 5;

        List<Boolean> objectAtEdges = new ArrayList<Boolean>(list.size());
        for (int i = 0; i < list.size(); i++) {
            Detection detection = list.get(i);
            RectF boundingBox = detection.getBoundingBox();
            float left = boundingBox.left;
            float top = boundingBox.top;
            float right = boundingBox.left + boundingBox.width();
            float buttom = boundingBox.top + boundingBox.height();

            float distToLeft = left;
            float distToRight = maxImageWidth - right;
            float distToTop = top;
            float distToBottom = maxImageHeight - buttom;
            boolean atEdge = false;
            float halfWidth = maxImageWidth / 2;
            if (boundingBox.width() < halfWidth && ((distToLeft <= paddingWith) || (distToRight <= paddingWith) || (distToTop <= paddingWith) || (distToBottom <= paddingWith))) {
                atEdge = true;
            }
            objectAtEdges.add(atEdge);
        }
        boolean allTrue = objectAtEdges.stream().allMatch(Boolean::booleanValue);
        return allTrue;
    }

    /**
     * 缩放图像，如果需要缩放，就顺便把宽高对齐给做了
     *
     * @param bitmap    原图
     * @param maxWidth  最大目标宽度
     * @param maxHeight 最大目标高度
     * @return 缩放后的图像
     */
    private static Bitmap scaleBitmap(Bitmap bitmap, int maxWidth, int maxHeight) {
        float horizontalScale = ((float) bitmap.getWidth()) / maxWidth;
        float verticalScale = ((float) bitmap.getHeight()) / maxHeight;
        if (horizontalScale < 1 || verticalScale < 1) {
            return bitmap;
        }
        float maxScale = Math.max(horizontalScale, verticalScale);
        // 确保为4的倍数
        int newWidth = (int) (bitmap.getWidth() / maxScale) & ~0b11;
        int newHeight = (int) (bitmap.getHeight() / maxScale) & ~0b11;

        return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
    }

}
